package com.restphp.java.hashdb.core;

import com.alibaba.fastjson.JSONObject;
import com.restphp.java.hashdb.utils.FileUtils;
import com.restphp.java.hashdb.utils.MD5Utils;
import io.netty.util.internal.logging.Log4J2LoggerFactory;
import lombok.Synchronized;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;

/**
 *
 * Created by sofical on 2020/8/3.
 */
public class HashDbCore {
    final private static String storeKeyData = "_data";
    final private static String storeKeyExpire = "_expire";
    final private static String storeKyeTime = "_time";
    final private static String file = "_table_.db";
    final private static Integer defaultSaveInterval = 5;

    private static LinkedHashMap<String, Object> cacheMap = new LinkedHashMap<>();
    private static LinkedHashMap<String, String> tableMap = null;

    public static Boolean running = false;

    final private static Logger logger = Logger.getGlobal();

    /**
     * 启动.
     * @param root
     * @param saveInterval
     */
    public static void startup(String root, Integer saveInterval) {
        // 在新线中启触发启动
        new Thread(new Runnable()  {
            @Override
            public void run() {
                startupReal(root, saveInterval);
            }
        }).start();
    }

    /**
     * 启动DB初始化任务和落地保存任务.
     */
    @Synchronized
    private static void startupReal(String root, Integer saveInterval) {
        if (running) {
            return;
        }
        running = true;

        // 初始化旧数据.
        String content = FileUtils.get(root + "/" + file);
        if (isBlank(content)) {
            if (null == tableMap) {
                tableMap = new LinkedHashMap<>();
            }
        } else {
            tableMap = JSONObject.parseObject(content, LinkedHashMap.class);
        }

        for (Map.Entry<String, String> item : tableMap.entrySet()) {
            String con = FileUtils.get(item.getValue());
            if (isBlank(con)) {
                cacheMap.put(item.getKey(), new LinkedHashMap<>());
                continue;
            }
            cacheMap.put(item.getKey(), JSONObject.parseObject(con, LinkedHashMap.class));
        }

        if (null == saveInterval) {
            saveInterval = defaultSaveInterval;
        }

        // 启动自动存储任务.
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //System.out.println(System.currentTimeMillis() / 1000);
                save();
            }
        }, 1, saveInterval * 1000);
    }

    private static Boolean isBlank(String str){
        return (str==null) || (str.length()==0) || (str.trim().length()==0);
    }

    private static Boolean waiting = false;

    /**
     * 抛弃式排队，只留最后一位.
     */
    private  static void queue() {
        if (waiting) {
            return;
        }
        waiting = true;

        try {
            Thread.sleep(500);
            save();
        } catch (Exception e) {

        } finally {
            waiting = false;
        }
    }

    /**
     * 保存处理锁.
     */
    private static Boolean saving = false;

    /**
     * 落地保存.
     */
    private static void save() {
        if (saving) {
            queue();
            return;
        }
        saving = true;

        try {
            for (Map.Entry<String, Object> tableItem : cacheMap.entrySet()) {
                Map<String, Object> tableData = (Map) tableItem.getValue();
                for (Map.Entry<String, Object> cacheItem : tableData.entrySet()) {
                    Map<String, Object> cacheData = (Map) cacheItem.getValue();
                    Long expire = Long.valueOf(String.valueOf(cacheData.get(storeKeyExpire)));
                    if (expire > 0 && expire < System.currentTimeMillis()) {
                        cacheData.remove(cacheItem.getKey());
                    }
                    tableData.put(cacheItem.getKey(), cacheData);
                }
                cacheMap.put(tableItem.getKey(), tableData);
                String tableFile = tableMap.get(tableItem.getKey());
                String content = JSONObject.toJSONString(tableData);
                FileUtils.save(content, tableFile);
            }
        }catch (Exception e) {
            queue();
        } finally {
            saving = false;
        }
    }

    /**
     * 获取表ID.
     * @param root
     * @param table
     * @param save
     * @return
     */
    private static String getTableId (String root, String table, Boolean save) {
        String dbFile = root + "/" + table + ".db";
        //String id = MD5Utils.encode(dbFile);
        String id = dbFile.replaceAll("\\\\", "/")
                .replaceAll("\\/", "_")
                .replaceAll("\\.", "_")
                .replaceAll("\\:", "_");
        if (save) {
            tableMap.put(id, dbFile);
            String content = JSONObject.toJSONString(tableMap);
            FileUtils.save(content, root + "/" + file);
        }
        return id;
    }

    public static void mainX(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        String id = "E:\\project\\winiis\\xnh\\tmp/grade.db";
        id = id.replaceAll("\\\\", "/")
                .replaceAll("\\/", "_")
                .replaceAll("\\.", "_")
                .replaceAll("\\:", "_");
        System.out.println(id);
        Thread.sleep(3000);
        System.out.println(System.currentTimeMillis());
        MD5Utils.encode(id);
    }

    /**
     * set cache.
     * @param root 数据落地文件地址.
     * @param table table.
     * @param key key.
     * @param data data.
     * @param timeout time out.
     * @param setTime 设置时间.
     */
    public static void set(String root, String table, String key, Object data, Integer timeout, Long setTime) {
        String tableId = getTableId(root, table, true);

        LinkedHashMap<String, Object> tableData = cacheMap.containsKey(tableId) ? ((LinkedHashMap<String, Object>) cacheMap.get(tableId)) : (new LinkedHashMap<>());

        //弱数据一致性处理，以最后设置时间为准.
        if (tableData.containsKey(key)) {
            Map<String, Object> cacheData = (Map) tableData.get(key);
            if (cacheData.containsKey(storeKyeTime)) {
                Long time = Long.valueOf(String.valueOf(cacheData.get(storeKyeTime)));
                if (setTime < time) {
                    return;
                }
            }
        }

        Map<String, Object> cacheData = new LinkedHashMap<>();
        cacheData.put(storeKeyData, data);
        cacheData.put(storeKeyExpire, timeout > 0 ? (System.currentTimeMillis() + (timeout * 1000L)) : 0);
        cacheData.put(storeKyeTime, setTime);
        tableData.put(key, cacheData);

        cacheMap.put(tableId, tableData);
    }

    /**
     * get cache data.
     * @param root 数据落地文件地址.
     * @param table table.
     * @param key key.
     * @param <T> class.
     * @return T.
     */
    public static <T> T get(String root, String table, String key, Class<T> tClass) {
        String tableId = getTableId(root, table, false);
        if (!cacheMap.containsKey(tableId)) {
            return null;
        }

        Map<String, Object> tableData = (Map) cacheMap.get(tableId);
        if (!tableData.containsKey(key)) {
            return null;
        }

        Map<String, Object> cacheData = (Map) tableData.get(key);
        Long expire = Long.valueOf(String.valueOf(cacheData.get(storeKeyExpire)));
        if (expire < System.currentTimeMillis() && expire > 0) {
            tableData.remove(key);
            cacheMap.put(tableId, tableData);
            return null;
        }
        return JSONObject.parseObject(JSONObject.toJSONString(cacheData.get(storeKeyData)), tClass);
    }

    /**
     * 删除缓存数据.
     * @param root 数据落地文件地址.
     * @param table 表名.
     * @param key key.
     * @param setTime set time.
     */
    public static void delete(String root, String table, String key, Long setTime) {
        String tableId = getTableId(root, table, false);
        if (!cacheMap.containsKey(tableId)) {
            return;
        }

        Map<String, Object> tableData = (Map) cacheMap.get(tableId);
        if (tableData.containsKey(key)) {

            //弱数据一致性处理，以最后设置时间为准.
            Map<String, Object> cacheData = (Map) tableData.get(key);
            Long time = Long.valueOf(String.valueOf(cacheData.get(storeKyeTime)));
            if (setTime < time) {
                return;
            }

            tableData.remove(key);
            cacheMap.put(tableId, tableData);
        }
    }
}
